/** @file    AlarmBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: AlarmBundle.cpp 2873 2010-08-08 19:06:41Z alex $
 *  @brief
 * File containing methods for the wosh::services::AlarmBundle class.
 * The header for this class can be found in AlarmBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "AlarmBundle.h"

 #include <framework/security/AlarmEngine.h>

 #include <core/ObjectAllocator.h>
 #include <core/MethodsCommon.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/Fact.h>
 #include <core/List.h>
 #include <core/Table.h>
 #include <core/SecurityManager.h>
 #include <core/PersistenceManager.h>
 #include <core/FileSystem.h>
 #include <core/SystemError.h>


using namespace std;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::AlarmBundle, "wosh::interfaces::services::Alarm", _Alarm_VERSION, _static_AlarmBundle1 )
 WOSH_REGISTER(wosh::services::AlarmBundle, "wosh::BundleGeneric", _Alarm_VERSION, _static_AlarmBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

AlarmBundle::AlarmBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _Alarm_NAME, false );
	Properties.update( _KEY_Version, _Alarm_VERSION );

//Log.setLevel(LOG_VERBOSE);

	Log(LOG_DEBUG, " Configuring Alarm worker.." );
	this->alarmEngine = new AlarmEngine();
	this->alarmEngine->setAlarmListener(this);

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _Alarm_KEY_DB,	"$DATABASE/alarm/default.xml", Permission(Permission::Read) );

	Properties.set( _Alarm_KEY_Configuration, "", Permission(Permission::RX, Permission::Read, Permission::Read) );

	Properties.set( _Alarm_KEY_AlarmState, "", Permission(Permission::Read) );
	Properties.set( _Alarm_KEY_AlarmStateTs, Utilities::std_time(), Permission(Permission::Read) );

	Properties.set( _Alarm_KEY_Events, 0L, Permission(Permission::Read) );
	Properties.set( _Alarm_KEY_LastEvent, "", Permission(Permission::Read) );
	Properties.set( _Alarm_KEY_LastEventTs, Utilities::std_time(), Permission(Permission::Read) );

	Properties.set( _Alarm_KEY_Errors, 0L, Permission(Permission::Read) );
	Properties.set( _Alarm_KEY_LastError, "", Permission(Permission::Read) );
	Properties.set( _Alarm_KEY_LastErrorTs, Utilities::std_time(), Permission(Permission::Read) );

	Log(LOG_DEBUG, " Registering methods.." );
	MethodRequest* mmArmAlarm = Methods.create<MethodRequest>( _Alarm_METHOD_arm, "Arm alarm" );
	mmArmAlarm->setMethod( this, (MethodRequestPtr)&AlarmBundle::mmDoArmAlarm  );
	mmArmAlarm->getPermission().setMask( Permission::RX, Permission::RX, Permission::Read );
	MethodRequest* mmDisarmAlarm = Methods.create<MethodRequest>( _Alarm_METHOD_disarm, "Disarm alarm" );
	mmDisarmAlarm->setMethod( this, (MethodRequestPtr)&AlarmBundle::mmDoDisarmAlarm );
	mmDisarmAlarm->getPermission().setMask( Permission::RX, Permission::RX, Permission::Read );

	MethodRequest* mmGetAlarmTriggers = Methods.create<MethodRequest>( _Alarm_METHOD_get_triggers, "Get Triggers" );
	mmGetAlarmTriggers->setMethod( this, (MethodRequestPtr)&AlarmBundle::mmDoGetAlarmTriggers );
	mmGetAlarmTriggers->getPermission().setMask( Permission::RX, Permission::RX, Permission::Read );

	MethodRequest* mmGetAlarmEvents = Methods.create<MethodRequest>( _Alarm_METHOD_get_events, "Get Events" );
	mmGetAlarmEvents->setMethod( this, (MethodRequestPtr)&AlarmBundle::mmDoGetAlarmEvents );
	mmGetAlarmEvents->getPermission().setMask( Permission::RX, Permission::RX, Permission::Read );

	setBundleState(Bundle::STATE_CREATED, false);
 }


AlarmBundle::~AlarmBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~AlarmBundle() : Destroying while RUNNING! Trying to stop.." );
		bundleStop();
	 }
	delete this->alarmEngine; this->alarmEngine = NULL;
	Log(LOG_VERBOSE, ":~AlarmBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT AlarmBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	BusDevices.setMessageHandler(this);
	WRESULT bus_d_connected = BusDevices.connect( _Bus_Devices, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_d_connected) )
		Log(LOG_CRITICAL, ":bundleStart() : FAILED#%d Connecting Bus ("_Bus_Devices")..", bus_d_connected );

	BusBuilding.setMessageHandler(this);
	WRESULT bus_b_connected = BusBuilding.connect( _Bus_Building, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_b_connected) )
		Log(LOG_CRITICAL, ":bundleStart() : FAILED#%d Connecting Bus (wosh.Bus.Building)..", bus_b_connected );

	loadConfiguration();

	WRESULT ret = this->alarmEngine->arm();

	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: thread_event()
	return ret;
 }

WRESULT AlarmBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	saveConfiguration();

	WRESULT ret = this->alarmEngine->disarm();

	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: thread_event()
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void AlarmBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	// avoid message replicas
	if ( !MessageFilter::filterReplica( message, &BusCore ) )
		return;
	BundleGeneric::busMessage(message, source);
	if ( this->isBundleRunning() )
		this->alarmEngine->evalMessage( message );
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT

bool AlarmBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source ) {
	// forward processing to base class
	return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////// ENGINE EVENTS

void AlarmBundle::alarm_armed( AlarmEngine* ) {
	setBundleState( Bundle::STATE_STARTED );

	Fact* fact = new Fact( _Alarm_EVENT_Armed );
	Message* msg_event = new Message(fact);
	msg_event->setDestinationBroadcast(true);
	signMessage(msg_event);
	BusCore.postMessage(msg_event);
 }

void AlarmBundle::alarm_disarmed( AlarmEngine* )
 {
	setBundleState( Bundle::STATE_STOPPED );

	Fact* fact = new Fact( _Alarm_EVENT_Disarmed );
	Message* msg_event = new Message(fact);
	msg_event->setDestinationBroadcast(true);
	signMessage(msg_event);
	BusCore.postMessage(msg_event);

 }

void AlarmBundle::postMessage( Message* message, AlarmEngine* )
 {
	if ( message == NULL ) return;
	message->setDestinationBroadcast(true);
	signMessage(message);

	if ( message->getSourceBus() == _Bus_Core )
		BusCore.postMessage(message);
	else if ( message->getSourceBus() == _Bus_Devices )
		BusDevices.postMessage(message);
	else if ( message->getSourceBus() == _Bus_Building )
		BusBuilding.postMessage(message);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void AlarmBundle::changed_AlarmState( int state, AlarmState* alarm_state )
 {
	if ( alarm_state == NULL ) {
		return;
	 }
	// update fields, don't raise updatingProperties!
	Properties.transactionBeginWrite();
	Properties.update(_Alarm_KEY_AlarmState, state );
	Properties.update(_Alarm_KEY_AlarmStateTs, Utilities::std_time() );
	Properties.transactionEnd();

	Fact* fact = new Fact( _Alarm_EVENT_StateChanged, new Variant(state) );
	Message* msg_event = new Message(fact);
	msg_event->setDestinationBroadcast(true);
	signMessage(msg_event);
	BusCore.postMessage(msg_event);
 }

void AlarmBundle::raised_AlarmEvent( const AlarmEvent* alarm_event, AlarmState* )
 {
	if ( alarm_event == NULL ) {
		return;
	 }
	// update fields, don't raise updatingProperties!
	Properties.transactionBeginWrite();
	Properties.update(_Alarm_KEY_Events, -1 );
	Properties.update(_Alarm_KEY_LastEvent, alarm_event->getDescription() );
	Properties.update(_Alarm_KEY_LastEventTs, Utilities::std_time() );
	Properties.transactionEnd();

	Fact* fact = new Fact( _Alarm_EVENT_AlarmEvent, alarm_event->clone() );
	Message* msg_event = new Message(fact);
	msg_event->setDestinationBroadcast(true);
	signMessage(msg_event);
	BusCore.postMessage(msg_event);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void AlarmBundle::raised_AlarmBrain_Error( int error_id, const std::string& description, AlarmBrain* )
 {
	// update fields, don't raise updatingProperties!
	Properties.transactionBeginWrite();
	Properties.update(_Alarm_KEY_Errors, error_id );
	Properties.update(_Alarm_KEY_LastError, description );
	Properties.update(_Alarm_KEY_LastErrorTs, Utilities::std_time() );
	Properties.transactionEnd();

	SystemError::raise(this, Log, SystemError::CRITICAL, _Alarm_ERROR_BRAIN, "raised_AlarmBrain_Error", "Error#%d [%s]!", error_id, description.c_str() );
 }

/////////////////////////////////////////////////////////////////////////////////////////////// ENGINE EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT AlarmBundle::loadConfiguration()
 {
	WRESULT ret = WRET_ERR_INTERNAL;
	if ( WFAILED(ret) ) {
//		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "loadConfiguration", "FAILED#%d loading Database %s..", ret, xmlfile.c_str() );
		return ret;
	 }
/*	if ( objects.size() == 0 ) {
		Log(LOG_WARNING, ":loadConfiguration() : No Items found in database.." );
		return WRET_OK;
	 }
	// register objects
	std::vector<ISerializable*>::iterator it;
	for ( it=objects.begin(); it!=objects.end(); it++ ) {
		AlarmTrigger* currObj = dynamic_cast<AlarmTrigger*>(*it);
		if ( currObj == NULL ) continue;
		this->alarmEngine->addTrigger(currObj);
	 }
 */
	return ret;
 }

WRESULT AlarmBundle::saveConfiguration()
 {

	return WRET_ERR_INTERNAL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* AlarmBundle::mmDoArmAlarm( const Request* request )
 {
	if ( request == NULL ) return NULL;
	std::string options = request->getArgument().toString();
	Log(LOG_VERBOSE, "mmDoArmAlarm(%s)", options.c_str() );

	WRESULT ret = arm();

	return request->replyResponse(ret);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* AlarmBundle::mmDoDisarmAlarm( const Request* request )
 {
	if ( request == NULL ) return NULL;
	std::string options = request->getArgument().toString();
	Log(LOG_VERBOSE, "mmDoDisarmAlarm(%s)", options.c_str() );

	WRESULT ret = disarm();

	return request->replyResponse(ret);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* AlarmBundle::mmDoGetAlarmTriggers( const Request* request )
 {
	if ( request == NULL ) return NULL;
	std::string option = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmDoGetAlarmTriggers(%s)", option.c_str() );

	if ( option == "s" || option == "short" ) {
		this->alarmEngine->getAlarmTriggers().transactionBeginRead();
		List* objs = new List(this->alarmEngine->getAlarmTriggers().size());
		AlarmEngine::tAlarmTriggerList::Iterator it;
		const AlarmEngine::tAlarmTriggerList::ConstIterator it_end = this->alarmEngine->getAlarmTriggers().end();
		for ( it=this->alarmEngine->getAlarmTriggers().begin(); it!=it_end; ++it ) {
			if ( *it == NULL ) continue;
			objs->push_back<Variant>( (*it)->getID() );
		 }
		this->alarmEngine->getAlarmTriggers().transactionEnd();
		return request->replyResponse(WRET_OK, objs);
	 }
	else {
		this->alarmEngine->getAlarmTriggers().transactionBeginRead();
		Table* tbl = new Table( this->alarmEngine->getAlarmTriggers().size(), 3, true );
		tbl->setTableName("Triggers");
		tbl->setHeaderHorizontal("ID", 0);
		tbl->setHeaderHorizontal("Type", 1);
		tbl->setHeaderHorizontal("Level", 2);
		unsigned int iRow = 0;
		AlarmEngine::tAlarmTriggerList::Iterator it;
		const AlarmEngine::tAlarmTriggerList::ConstIterator it_end = this->alarmEngine->getAlarmTriggers().end();
		for ( it=this->alarmEngine->getAlarmTriggers().begin(); it!=it_end; ++it, ++iRow ) {
			if ( *it == NULL ) continue;
			tbl->set<Variant>( (*it)->getID(), iRow, 0);
			tbl->set<Variant>( (*it)->getClassName(), iRow, 1);
			tbl->set<Variant>( (*it)->getAlarmLevel(), iRow, 2);
		 }
		this->alarmEngine->getAlarmTriggers().transactionEnd();
		return request->replyResponse(WRET_OK, tbl);
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* AlarmBundle::mmDoGetAlarmEvents( const Request* request )
 {
	if ( request == NULL ) return NULL;
	std::string option = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmDoGetAlarmEvents(%s)", option.c_str() );

	if ( option == "s" || option == "short" ) {
		List* objs = new List();
		this->alarmEngine->getAlarmState()->getEvents().transactionBeginRead();
		AlarmState::tAlarmEventList::Iterator it;
		const AlarmState::tAlarmEventList::ConstIterator it_end = this->alarmEngine->getAlarmState()->getEvents().end();
		for ( it=this->alarmEngine->getAlarmState()->getEvents().begin(); it!=it_end; ++it ) {
			if ( *it == NULL ) continue;
			objs->push_back<Variant>( (*it)->getID() );
		 }
		this->alarmEngine->getAlarmState()->getEvents().transactionEnd();
		return request->replyResponse(WRET_OK, objs);
	 }
	else {
		this->alarmEngine->getAlarmState()->getEvents().transactionBeginRead();
		Table* tbl = new Table( this->alarmEngine->getAlarmState()->getEvents().size(), 4, true );
		tbl->setTableName("Events");
		tbl->setHeaderHorizontal("ID", 0);
		tbl->setHeaderHorizontal("TimeStamp", 1);
		tbl->setHeaderHorizontal("Level", 2);
		tbl->setHeaderHorizontal("Description", 3);
		unsigned int iRow = 0;
		AlarmState::tAlarmEventList::Iterator it;
		const AlarmState::tAlarmEventList::ConstIterator it_end = this->alarmEngine->getAlarmState()->getEvents().end();
		for ( it=this->alarmEngine->getAlarmState()->getEvents().begin(); it!=it_end; ++it, ++iRow ) {
			if ( *it == NULL ) continue;
			tbl->set<Variant>( (*it)->getID(), iRow, 0);
			tbl->set( new Variant((*it)->getTimeStamp(), Variant::DATETIME), iRow, 1);
			tbl->set<Variant>( (*it)->getAlarmLevel(), iRow, 2);
			tbl->set<Variant>( (*it)->getDescription(), iRow, 3);
		 }
		this->alarmEngine->getAlarmState()->getEvents().transactionEnd();
		return request->replyResponse(WRET_OK, tbl);
	 }
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
